/*
 * Socket.cpp
 *
 */

#include "Socket.h"
#include <string.h>

using std::string;

SocketException::SocketException(const string& msg) throw() {
	this->msg = msg;
}

const char* SocketException::what() const throw() {
	return msg.c_str();
}

Socket::Socket(int sock) {
	this->sock = sock;
}

Socket::Socket() {
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == -1)
		throw SocketException("Fallo constructor");
}

Socket::~Socket() {
	close();
}

void Socket::close() {
	Lock lock(sock_mutex);
	if (sock != -1) {
		::close(sock);
		sock = -1;
	}
}

int Socket::recv(void* dato, int tamanioDato) {
	int x;
	if ((x = ::recv(sock, dato, tamanioDato, 0)) <= 0)
		throw SocketException("Fallo recv");
	else
		return x;
}

int Socket::send(const void* dato, int tamanioDato) {
	int x;
	if ((x = ::send(sock, dato, tamanioDato, 0)) < 0)
		throw SocketException("Fallo send");
	else
		return x;
}

std::string Socket::getIP() {
	socklen_t len;
	struct sockaddr_storage addr;
	char ipstr[INET6_ADDRSTRLEN];

	len = sizeof addr;
	::getpeername(sock, (struct sockaddr*)&addr, &len);

	// deal with both IPv4 and IPv6:
	if (addr.ss_family == AF_INET) {
	    struct sockaddr_in *s = (struct sockaddr_in *)&addr;
	    inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
	} else {  // AF_INET6
	    struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
	    inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
	}
	return std::string(ipstr);
}

void Socket::shutdown() {
	Lock lock(sock_mutex);
	if (sock != -1)
		::shutdown(sock, SHUT_RDWR);
}

void ServerSocket::bind(unsigned short puerto) {
	sockaddr_in local;
	memset((char*)&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(puerto);
	local.sin_addr.s_addr = htonl(INADDR_ANY);

	// Setea el socket para ser reutilizado
	int on = 1;
	::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	if (::bind(sock, (sockaddr *) &local, sizeof(sockaddr_in)) < 0){
		throw SocketException("Fallo bind (1)");
	}
}

void ServerSocket::bind(const string& direccion, unsigned short puerto) {
	sockaddr_in local;
	memset((char*)&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_port = htons(puerto);
	local.sin_addr.s_addr = inet_addr(direccion.c_str());
	if (::bind(sock, (struct sockaddr *)&local, sizeof(struct sockaddr)) < 0)
		throw SocketException("Fallo bind");
}

void ServerSocket::listen(int cola) {
	if (::listen(sock, cola) < 0)
		throw SocketException("Fallo listen");
}

ServerSocket::ServerSocket(const string& direccion,
		unsigned short puerto, int cola) {
	bind(direccion, puerto);
	listen(cola);
}

ServerSocket::ServerSocket(unsigned short puerto, int cola):
Socket() {
	bind(puerto);
	listen(cola);
}

Socket* ServerSocket::accept() {
	int sd = ::accept(sock, NULL, 0);
	if(sd < 0)
		throw SocketException("Fallo accept");
	else
		return new Socket(sd);
}

void ServerSocket::unbind() {
	int on = 1;
	if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
			(char*)&on, sizeof(on)) < 0) {
		throw SocketException("Fallo unbind");
	}
}

ClientSocket::ClientSocket(const string& direccion, unsigned short puerto) {
	connect(direccion, puerto);
}

void ClientSocket::connect(const string& direccion, unsigned short puerto) {
	hostent* he = gethostbyname(direccion.c_str());
	if (he) {
		sockaddr_in addr;
		memset((char*)&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_port = htons(puerto);
		addr.sin_addr = *((struct in_addr *)he->h_addr);

		if (::connect(sock, (sockaddr *)&addr, sizeof(addr)) < 0)
			throw SocketException("Fallo connect");
	}
	else
	 	throw SocketException("Fallo gethostbyname");
}
